"""BLE File Transfer Service for large file transfers.

Provides GATT characteristics and high-level APIs for
file transfer with resume capability.
"""

import asyncio
import logging
import json
import struct
import uuid
import os
from typing import Optional, Callable, Dict, Any, List
from enum import Enum
from pathlib import Path

from ..large_data_handler import (
    LargeDataHandler,
    TransferSession,
    TransferPriority,
    ChunkMetadata
)
from ..fragmentation import FragmentationProtocol

logger = logging.getLogger(__name__)


# Service and Characteristic UUIDs
FILE_TRANSFER_SERVICE_UUID = "0000FF40-0000-1000-8000-00805F9B34FB"
FILE_METADATA_UUID = "0000FF41-0000-1000-8000-00805F9B34FB"  # Read/Write
FILE_DATA_UUID = "0000FF42-0000-1000-8000-00805F9B34FB"      # Write/Notify
FILE_CONTROL_UUID = "0000FF43-0000-1000-8000-00805F9B34FB"   # Write
FILE_STATUS_UUID = "0000FF44-0000-1000-8000-00805F9B34FB"    # Read/Notify


class FileTransferCommand(Enum):
    """File transfer control commands."""
    START_SEND = 0x01
    START_RECEIVE = 0x02
    PAUSE = 0x03
    RESUME = 0x04
    CANCEL = 0x05
    LIST_SESSIONS = 0x06
    DELETE_FILE = 0x07
    GET_FILE_INFO = 0x08


class FileTransferState(Enum):
    """File transfer states."""
    IDLE = 0
    PREPARING = 1
    SENDING = 2
    RECEIVING = 3
    PAUSED = 4
    COMPLETED = 5
    ERROR = 6
    CANCELLED = 7


class FileMetadata:
    """File metadata structure."""
    
    def __init__(self, filename: str, size: int, checksum: str = "",
                 mime_type: str = "application/octet-stream"):
        """Initialize file metadata.
        
        Args:
            filename: File name
            size: File size in bytes
            checksum: SHA256 checksum (optional)
            mime_type: MIME type
        """
        self.filename = filename
        self.size = size
        self.checksum = checksum
        self.mime_type = mime_type
        self.creation_time = 0
        self.modification_time = 0
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return {
            "filename": self.filename,
            "size": self.size,
            "checksum": self.checksum,
            "mime_type": self.mime_type,
            "creation_time": self.creation_time,
            "modification_time": self.modification_time
        }
    
    def to_bytes(self) -> bytes:
        """Serialize to bytes."""
        return json.dumps(self.to_dict()).encode('utf-8')
    
    @classmethod
    def from_bytes(cls, data: bytes) -> 'FileMetadata':
        """Deserialize from bytes."""
        metadata_dict = json.loads(data.decode('utf-8'))
        metadata = cls(
            metadata_dict["filename"],
            metadata_dict["size"],
            metadata_dict.get("checksum", ""),
            metadata_dict.get("mime_type", "application/octet-stream")
        )
        metadata.creation_time = metadata_dict.get("creation_time", 0)
        metadata.modification_time = metadata_dict.get("modification_time", 0)
        return metadata


class FileTransferService:
    """BLE File Transfer Service implementation."""
    
    def __init__(self, base_directory: str = ".", 
                 max_file_size: int = 10 * 1024 * 1024):  # 10MB default
        """Initialize file transfer service.
        
        Args:
            base_directory: Base directory for file operations
            max_file_size: Maximum file size allowed
        """
        self.base_directory = Path(base_directory).resolve()
        self.max_file_size = max_file_size
        
        # Create large data handler with fragmentation protocol
        self.protocol = FragmentationProtocol()
        self.data_handler = LargeDataHandler(self.protocol)
        
        # Service state
        self.state = FileTransferState.IDLE
        self.current_file_metadata: Optional[FileMetadata] = None
        self.current_session_id: Optional[str] = None
        
        # File transfer tracking
        self.sent_files: List[str] = []
        self.received_files: List[str] = []
        
        # Callbacks
        self.notify_callback: Optional[Callable[[str, bytes], None]] = None
        self.progress_callback: Optional[Callable[[str, float], None]] = None
        
        # Setup data handler callbacks
        self.data_handler.progress_callback = self._on_transfer_progress
        self.data_handler.completion_callback = self._on_transfer_complete
        
        # Service registration data
        self.service_data = self._create_service_definition()
        
        # Ensure base directory exists
        self.base_directory.mkdir(parents=True, exist_ok=True)
        
        logger.info(f"File Transfer Service initialized in {self.base_directory}")
    
    def _create_service_definition(self) -> Dict[str, Any]:
        """Create GATT service definition.
        
        Returns:
            Service definition dictionary
        """
        return {
            "uuid": FILE_TRANSFER_SERVICE_UUID,
            "primary": True,
            "characteristics": [
                {
                    "uuid": FILE_METADATA_UUID,
                    "flags": ["read", "write"],
                    "value": bytearray(512),
                    "descriptors": []
                },
                {
                    "uuid": FILE_DATA_UUID,
                    "flags": ["write", "notify"],
                    "value": bytearray(4096),
                    "descriptors": [
                        {
                            "uuid": "00002902-0000-1000-8000-00805F9B34FB",
                            "flags": ["read", "write"],
                            "value": bytearray([0x00, 0x00])
                        }
                    ]
                },
                {
                    "uuid": FILE_CONTROL_UUID,
                    "flags": ["write"],
                    "value": bytearray(64),
                    "descriptors": []
                },
                {
                    "uuid": FILE_STATUS_UUID,
                    "flags": ["read", "notify"],
                    "value": bytearray(128),
                    "descriptors": [
                        {
                            "uuid": "00002902-0000-1000-8000-00805F9B34FB",
                            "flags": ["read", "write"],
                            "value": bytearray([0x00, 0x00])
                        }
                    ]
                }
            ]
        }
    
    async def handle_read_metadata(self) -> bytes:
        """Handle read from FILE_METADATA characteristic.
        
        Returns:
            Current file metadata or empty
        """
        try:
            if self.current_file_metadata:
                return self.current_file_metadata.to_bytes()
            return b'{"filename": "", "size": 0}'
            
        except Exception as e:
            logger.error(f"Error reading metadata: {e}")
            return b'{}'
    
    async def handle_write_metadata(self, value: bytes) -> bool:
        """Handle write to FILE_METADATA characteristic.
        
        Args:
            value: File metadata bytes
            
        Returns:
            True if handled successfully
        """
        try:
            self.current_file_metadata = FileMetadata.from_bytes(value)
            logger.info(f"Metadata set: {self.current_file_metadata.filename} "
                       f"({self.current_file_metadata.size} bytes)")
            
            # Validate file size
            if self.current_file_metadata.size > self.max_file_size:
                logger.error(f"File too large: {self.current_file_metadata.size} bytes")
                return False
            
            return True
            
        except Exception as e:
            logger.error(f"Error writing metadata: {e}")
            return False
    
    async def handle_write_data(self, value: bytes) -> bool:
        """Handle write to FILE_DATA characteristic.
        
        Args:
            value: File data chunk
            
        Returns:
            True if handled successfully
        """
        try:
            if self.state == FileTransferState.RECEIVING:
                # Pass data to fragmentation protocol
                self.protocol.receive(value)
                return True
            else:
                logger.warning("Not in receiving state")
                return False
                
        except Exception as e:
            logger.error(f"Error handling data write: {e}")
            return False
    
    async def handle_write_control(self, value: bytes) -> bool:
        """Handle write to FILE_CONTROL characteristic.
        
        Args:
            value: Control command
            
        Returns:
            True if handled successfully
        """
        try:
            if not value:
                return False
            
            command = FileTransferCommand(value[0])
            
            if command == FileTransferCommand.START_SEND:
                return await self._start_send(value[1:])
                
            elif command == FileTransferCommand.START_RECEIVE:
                return await self._start_receive(value[1:])
                
            elif command == FileTransferCommand.PAUSE:
                return await self._pause_transfer()
                
            elif command == FileTransferCommand.RESUME:
                return await self._resume_transfer(value[1:])
                
            elif command == FileTransferCommand.CANCEL:
                return await self._cancel_transfer()
                
            elif command == FileTransferCommand.LIST_SESSIONS:
                await self._list_sessions()
                return True
                
            elif command == FileTransferCommand.DELETE_FILE:
                return await self._delete_file(value[1:])
                
            elif command == FileTransferCommand.GET_FILE_INFO:
                return await self._get_file_info(value[1:])
            
            return False
            
        except Exception as e:
            logger.error(f"Error handling control command: {e}")
            return False
    
    async def handle_read_status(self) -> bytes:
        """Handle read from FILE_STATUS characteristic.
        
        Returns:
            Status information
        """
        try:
            # Get current session
            session = None
            if self.current_session_id:
                session = self.data_handler.get_session(self.current_session_id)
            
            progress = session.progress if session else 0.0
            transferred = session.transferred if session else 0
            total_size = session.total_size if session else 0
            
            # Get performance stats
            stats = self.data_handler.get_stats()
            
            status = {
                "state": self.state.name,
                "progress": int(progress),
                "transferred": transferred,
                "total_size": total_size,
                "throughput": int(stats["metrics"]["throughput"]),
                "session_id": self.current_session_id or "",
                "filename": self.current_file_metadata.filename if self.current_file_metadata else ""
            }
            
            return json.dumps(status).encode('utf-8')[:128]  # Max 128 bytes
            
        except Exception as e:
            logger.error(f"Error reading status: {e}")
            return b'{"state": "ERROR"}'
    
    async def send_file(self, file_path: str, 
                       priority: TransferPriority = TransferPriority.NORMAL) -> bool:
        """Send file via BLE.
        
        Args:
            file_path: Path to file to send
            priority: Transfer priority
            
        Returns:
            True if successful
        """
        try:
            full_path = self.base_directory / file_path
            
            if not full_path.exists():
                logger.error(f"File not found: {file_path}")
                return False
            
            # Create metadata
            file_size = full_path.stat().st_size
            if file_size > self.max_file_size:
                logger.error(f"File too large: {file_size} bytes")
                return False
            
            self.current_file_metadata = FileMetadata(
                filename=full_path.name,
                size=file_size
            )
            
            # Generate session ID
            self.current_session_id = str(uuid.uuid4())
            self.state = FileTransferState.SENDING
            
            # Send file through data handler
            success = await self.data_handler.send_file(
                str(full_path),
                self.current_session_id,
                priority
            )
            
            if success:
                self.sent_files.append(file_path)
                self.state = FileTransferState.COMPLETED
            else:
                self.state = FileTransferState.ERROR
            
            return success
            
        except Exception as e:
            logger.error(f"Send file error: {e}")
            self.state = FileTransferState.ERROR
            return False
    
    async def receive_file(self, save_path: str) -> bool:
        """Prepare to receive file.
        
        Args:
            save_path: Path to save received file
            
        Returns:
            True if ready to receive
        """
        try:
            if not self.current_file_metadata:
                logger.error("No file metadata set")
                return False
            
            full_path = self.base_directory / save_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            
            self.current_session_id = str(uuid.uuid4())
            self.state = FileTransferState.RECEIVING
            
            # Setup protocol callback for received data
            def on_data_received(data: bytes):
                # Write data to file
                with open(full_path, 'ab') as f:
                    f.write(data)
                logger.info(f"Received {len(data)} bytes, saved to {save_path}")
            
            self.protocol.receive_callback = on_data_received
            
            logger.info(f"Ready to receive {self.current_file_metadata.filename}")
            return True
            
        except Exception as e:
            logger.error(f"Receive file error: {e}")
            return False
    
    async def _start_send(self, params: bytes) -> bool:
        """Start file send operation.
        
        Args:
            params: Command parameters
            
        Returns:
            True if started successfully
        """
        if self.state != FileTransferState.IDLE:
            logger.warning("Transfer already in progress")
            return False
        
        # Extract filename from parameters
        filename = params.decode('utf-8').strip('\x00')
        if not filename:
            logger.error("No filename specified")
            return False
        
        return await self.send_file(filename)
    
    async def _start_receive(self, params: bytes) -> bool:
        """Start file receive operation.
        
        Args:
            params: Command parameters
            
        Returns:
            True if started successfully
        """
        if self.state != FileTransferState.IDLE:
            logger.warning("Transfer already in progress")
            return False
        
        # Extract save path from parameters
        save_path = params.decode('utf-8').strip('\x00')
        if not save_path:
            logger.error("No save path specified")
            return False
        
        return await self.receive_file(save_path)
    
    async def _pause_transfer(self) -> bool:
        """Pause current transfer.
        
        Returns:
            True if paused
        """
        if self.state in [FileTransferState.SENDING, FileTransferState.RECEIVING]:
            self.state = FileTransferState.PAUSED
            # Protocol doesn't have native pause, so we'll handle at service level
            logger.info("Transfer paused")
            return True
        return False
    
    async def _resume_transfer(self, params: bytes) -> bool:
        """Resume paused transfer.
        
        Args:
            params: Resume parameters (session ID)
            
        Returns:
            True if resumed
        """
        if self.state == FileTransferState.PAUSED:
            session_id = params.decode('utf-8').strip('\x00') if params else self.current_session_id
            
            if session_id and session_id in self.data_handler.sessions:
                self.current_session_id = session_id
                self.state = FileTransferState.SENDING  # or RECEIVING based on session
                logger.info(f"Transfer {session_id} resumed")
                return True
        
        return False
    
    async def _cancel_transfer(self) -> bool:
        """Cancel current transfer.
        
        Returns:
            True if cancelled
        """
        if self.current_session_id:
            success = self.data_handler.cancel_transfer(self.current_session_id)
            if success:
                self.state = FileTransferState.CANCELLED
                self.current_session_id = None
                logger.info("Transfer cancelled")
                return True
        
        return False
    
    async def _list_sessions(self):
        """List active transfer sessions."""
        sessions = []
        for session_id, session in self.data_handler.sessions.items():
            sessions.append({
                "session_id": session_id,
                "progress": session.progress,
                "total_size": session.total_size,
                "file_path": session.file_path
            })
        
        # Send as notification
        data = json.dumps({"sessions": sessions}).encode('utf-8')
        if self.notify_callback:
            self.notify_callback(FILE_STATUS_UUID, data[:128])
    
    async def _delete_file(self, params: bytes) -> bool:
        """Delete file.
        
        Args:
            params: Filename to delete
            
        Returns:
            True if deleted
        """
        try:
            filename = params.decode('utf-8').strip('\x00')
            file_path = self.base_directory / filename
            
            if file_path.exists():
                file_path.unlink()
                logger.info(f"File deleted: {filename}")
                return True
            else:
                logger.warning(f"File not found: {filename}")
                return False
                
        except Exception as e:
            logger.error(f"Delete file error: {e}")
            return False
    
    async def _get_file_info(self, params: bytes) -> bool:
        """Get file information.
        
        Args:
            params: Filename
            
        Returns:
            True if info sent
        """
        try:
            filename = params.decode('utf-8').strip('\x00')
            file_path = self.base_directory / filename
            
            if file_path.exists():
                stat = file_path.stat()
                info = {
                    "filename": filename,
                    "size": stat.st_size,
                    "creation_time": stat.st_ctime,
                    "modification_time": stat.st_mtime,
                    "exists": True
                }
            else:
                info = {"filename": filename, "exists": False}
            
            # Send as notification
            data = json.dumps(info).encode('utf-8')
            if self.notify_callback:
                self.notify_callback(FILE_STATUS_UUID, data[:128])
            
            return True
            
        except Exception as e:
            logger.error(f"Get file info error: {e}")
            return False
    
    def _on_transfer_progress(self, session_id: str, progress: float):
        """Handle transfer progress updates.
        
        Args:
            session_id: Session ID
            progress: Progress percentage
        """
        logger.debug(f"Transfer {session_id}: {progress:.1f}%")
        
        # Update state
        if progress >= 100.0:
            self.state = FileTransferState.COMPLETED
        
        # Send status notification
        asyncio.create_task(self._send_status_notification())
        
        # Call user callback
        if self.progress_callback:
            self.progress_callback(session_id, progress)
    
    def _on_transfer_complete(self, session_id: str, success: bool):
        """Handle transfer completion.
        
        Args:
            session_id: Session ID
            success: Whether successful
        """
        if success:
            self.state = FileTransferState.COMPLETED
            logger.info(f"Transfer {session_id} completed successfully")
            
            # Add to completed files list
            session = self.data_handler.get_session(session_id)
            if session and session.file_path:
                self.received_files.append(session.file_path)
        else:
            self.state = FileTransferState.ERROR
            logger.error(f"Transfer {session_id} failed")
        
        # Send final status notification
        asyncio.create_task(self._send_status_notification())
    
    async def _send_status_notification(self):
        """Send status update notification."""
        try:
            status_data = await self.handle_read_status()
            
            if self.notify_callback:
                self.notify_callback(FILE_STATUS_UUID, status_data)
                
        except Exception as e:
            logger.error(f"Error sending status notification: {e}")
    
    def set_notify_callback(self, callback: Callable[[str, bytes], None]):
        """Set notification callback.
        
        Args:
            callback: Callback for notifications (uuid, data)
        """
        self.notify_callback = callback
        
        # Also set for data handler protocol
        def protocol_send_callback(data: bytes):
            if self.notify_callback:
                self.notify_callback(FILE_DATA_UUID, data)
        
        self.protocol.set_callbacks(
            protocol_send_callback,
            self.protocol.receive_callback
        )
    
    def get_service_definition(self) -> Dict[str, Any]:
        """Get GATT service definition.
        
        Returns:
            Service definition for registration
        """
        return self.service_data
    
    def list_files(self) -> List[str]:
        """List files in base directory.
        
        Returns:
            List of filenames
        """
        try:
            files = []
            for file_path in self.base_directory.iterdir():
                if file_path.is_file():
                    files.append(file_path.name)
            return files
        except Exception as e:
            logger.error(f"List files error: {e}")
            return []
    
    def get_transfer_stats(self) -> Dict[str, Any]:
        """Get transfer statistics.
        
        Returns:
            Statistics dictionary
        """
        stats = self.data_handler.get_stats()
        stats.update({
            "service_state": self.state.name,
            "sent_files": len(self.sent_files),
            "received_files": len(self.received_files),
            "current_session": self.current_session_id,
            "available_files": len(self.list_files())
        })
        return stats
    
    async def cleanup(self):
        """Clean up service resources."""
        try:
            # Cancel any active transfers
            if self.current_session_id:
                await self._cancel_transfer()
            
            # Clean up data handler
            await self.data_handler.cleanup()
            
            # Reset state
            self.state = FileTransferState.IDLE
            self.current_file_metadata = None
            self.current_session_id = None
            
            logger.info("File Transfer Service cleaned up")
            
        except Exception as e:
            logger.error(f"Cleanup error: {e}")


# Example usage
if __name__ == "__main__":
    import asyncio
    import tempfile
    
    async def test_file_service():
        """Test file transfer service."""
        # Create temporary directory
        with tempfile.TemporaryDirectory() as temp_dir:
            service = FileTransferService(temp_dir)
            
            # Create test file
            test_file = Path(temp_dir) / "test.txt"
            test_data = b"Hello, File Transfer Service!" * 100  # 2.9KB
            with open(test_file, 'wb') as f:
                f.write(test_data)
            
            print(f"Created test file: {test_file.name} ({len(test_data)} bytes)")
            
            # List files
            files = service.list_files()
            print(f"Available files: {files}")
            
            # Send file
            success = await service.send_file("test.txt", TransferPriority.NORMAL)
            print(f"File send: {'successful' if success else 'failed'}")
            
            # Get stats
            stats = service.get_transfer_stats()
            print(f"\nTransfer Statistics:")
            print(f"  State: {stats['service_state']}")
            print(f"  Throughput: {stats['metrics']['throughput']:.2f} B/s")
            print(f"  Sent files: {stats['sent_files']}")
            
            # Cleanup
            await service.cleanup()
    
    # Run test
    asyncio.run(test_file_service())